根據 MDN 的教學
一開始canvas為空白,程式碼腳本需要先存取渲染環境,在上面繪圖,然後才會顯現影像。 素有一個方法(method)叫getContext(),透過此方法可以取得渲染環境及其繪圖函數(function);getContext()輸入參數只有渲染環境類型一項,像本教學所討論的2D繪圖,就是輸入”2d”。
所以當我們要在畫布上操作時,要將畫布改寫成方法,使用 getContext(),像是這樣
const ctx = canvasRef.current.getContext("2d");
再來按照畫筆的操作,行為如下:
開始畫畫(onMouseDown)-> 滑鼠按著移動繪製 (mousemove)-> 繪製完成(onMouseUp)
所以當滑鼠下壓時,我們應該給予一個 isDrawing = true;
,告訴下一個步驟,當前是在繪製狀態,同理,在繪製完成時 isDrawing = false;
這是本段的重點,當每個點在移動時進行繪製。
/**
* 移動時處理每一個點
* @param {Object} e 移動事件
*/
const handleMouseMove = (e: MouseEvent) => {
if (isDrawing) {
const point = { x: e.offsetX, y: e.offsetY };
handleDrawCanvas(point);
}
};
繪製
const handleDrawCanvas = (point: { x: number; y: number }) => {
const ctx = canvasRef.current.getContext("2d");
if (tool === "pencil") {
ctx.strokeStyle = activeColor;
ctx.lineWidth = 1;
// ctx.globalAlpha = opacity;
ctx.lineCap = "round"; // 頭尾圓弧的線條
ctx.lineJoin = "round"; // 交會時圓角
ctx.beginPath();
ctx.moveTo(lastPoint?.x, lastPoint?.y); // 下筆位置
ctx.lineTo(point?.x, point?.y);
ctx.stroke();
lastPoint = { x: point?.x, y: point?.y }; // 記錄最後停止位置
ctx.closePath();
}
};
每當一個點到下一個點時要記錄最後停止的位置,然後就可以點接著點連成線~
畫筆參數
在這邊學習幾個參數,可以依照喜好給予內容
ctx.strokeStyle = "#000"; // 畫筆顏色
ctx.lineWidth = 1; // 畫筆寬度
ctx.lineCap = "round"; // 頭尾圓弧的線條
ctx.lineJoin = "round"; // 交會時圓角
補上 MDN link
顏色:strokeStyle
畫筆粗細:lineWidth
頭尾處:lineCap
交界處:lineJoin
前面的準備就緒後,就可以開始用畫筆畫畫啦!來看看目前完成的程式碼
CanvasBox/index.tsx
/**
* 畫布區塊
*/
import { useEffect, useState, useRef, useCallback } from "react";
import { Wrapper, MainCanvas } from "./style";
import { useRecoilValue } from "recoil";
import { activeColorState, toolState } from "../../data/atom";
let lastPoint: { x?: number; y?: number } | null = {}; // 滑鼠移動的上一個點
const CanvasBox = () => {
const activeColor = useRecoilValue(activeColorState);
const tool = useRecoilValue(toolState);
const canvasRef = useRef<any>(null);
const [isDrawing, setIsDrawing] = useState<boolean>(false);
useEffect(() => {
const handleDrawCanvas = (point: { x: number; y: number }) => {
const ctx = canvasRef.current.getContext("2d");
if (tool === "pencil") {
ctx.strokeStyle = activeColor;
ctx.lineWidth = 1;
// ctx.globalAlpha = opacity;
ctx.lineCap = "round"; // 頭尾圓弧的線條
ctx.lineJoin = "round"; // 交會時圓角
ctx.beginPath();
ctx.moveTo(lastPoint?.x, lastPoint?.y); // 下筆位置
ctx.lineTo(point?.x, point?.y);
ctx.stroke();
lastPoint = { x: point?.x, y: point?.y };
}
ctx.closePath();
};
/**
* 移動時處理每一個點
* @param {Object} e 移動事件
*/
const handleMouseMove = (e: MouseEvent) => {
if (isDrawing) {
const point = { x: e.offsetX, y: e.offsetY };
handleDrawCanvas(point);
}
};
if (canvasRef && canvasRef.current) {
canvasRef.current.addEventListener("mousemove", handleMouseMove);
}
return () => {
canvasRef.current.removeEventListener("mousemove", handleMouseMove);
};
}, [isDrawing, canvasRef, activeColor, tool]);
/**
* 滑鼠點下畫布後開始畫畫 or 填滿
*/
const handleMouseDown = () => {
setIsDrawing(true);
};
/**
* 提起畫筆
*/
const handleMouseUp = () => {
if (isDrawing) {
setIsDrawing(false);
lastPoint = null;
}
};
return (
<Wrapper>
<MainCanvas
ref={canvasRef}
height={500}
width={500}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseOver={handleMouseUp}
></MainCanvas>
</Wrapper>
);
};
export default CanvasBox;
https://i.imgur.com/Iu3HlCQ.gif